/**
 * \brief mp4容器封装（复用），将hevc/h264等视频流、aac/mp3/ac3等音频流封装成mp4文件
 * \author 将狼才鲸
 * \date 2022-04-03
 * \note 参考自ffmpeg源码，路径libavformat/movenc.c
 */

#include "module.h"
#include <string.h> // memcpy
#include <stdio.h>  // printf fflush FILE

#include <stdint.h> // uin8_t uint16_t uint32_t uint64_t

#define AUDIO_USE_AAC
#define VIDEO_USE_H264

#define EPERM 1 /* Operation not permitted */
#define pr_info printf
#define MDATA_LEN (1 * 1024 * 1024)

static unsigned char amdata[MDATA_LEN];
int mp4amdatalen = 0; // 既作为长度，又作为消费者生产者的锁
static unsigned char vmdata[MDATA_LEN];
int mp4vmdatalen = 0; // 既作为长度，又作为消费者生产者的锁
static struct module *target = NULL;

#define debug printf
#define INT_MAX 2147483647
#define FIL FILE
#define MKTAG(a,b,c,d)  ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
#define FFMAX(a,b)      ((a) > (b) ? (a) : (b))
#define UINT64_C(x)     (x ## ULL)
#define AVERROR(e)      (-(e))  /* Returns a negative error code from a POSIX error code, to return from library functions */
#define MOV_TIMESCALE   1000
#define AV_PKT_FLAG_KEY 0x0001  /* The packet contains a keyframe */
#define AV_NOPTS_VALUE  ((int64_t)UINT64_C(0x8000000000000000))

// /* universal stream structure */
// typedef struct AVStream2 {
//     int index;  /**< stream index in AVFormatContext */
//     int id;     /* Format-specific stream ID. */

//     int64_t start_time;
//     int64_t duration;
//     int64_t nb_frames; ///< number of frames in this stream if known or 0
//     int64_t first_dts;
//     int64_t cur_dts;
// } AVStream2;

/* universal format structure, pointer in MOVMuxContext */
typedef struct AVFormatContext2 {
    FILE *pb;            /* file handle */
    void *priv_data;    /* determined format type, pointer of MOVMuxContext */

//     unsigned int nb_streams; /* stream number, normally 2: video and audio */
//     AVStream2 **streams;
//     int64_t start_time;
//     int64_t duration;
//     int64_t bit_rate;
//     int seek2any;
//     int probe_score;
} AVFormatContext2;

/* stream type */
enum AVMediaType2 {
    AVMEDIA_TYPE_UNKNOWN2 = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO2,
    AVMEDIA_TYPE_AUDIO2,
    AVMEDIA_TYPE_DATA2,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE2,
    AVMEDIA_TYPE_ATTACHMENT2,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB2
};

// /* stream codec type */
// enum AVCodecID2 {
//     AV_CODEC_ID_H264,
//     AV_CODEC_ID_MP3
// };

/* stream codec parameter */
typedef struct AVCodecParameters2 {
    enum AVMediaType2 codec_type;   /* stream codec type */
    int width;                      /* width for video */
    int height;                     /* height for video */
    int sample_rate;                /* sample rate for audio */

//     int channels;                /* channels for audio */
//     int codec_id;                /* enum AVCodecID */
//     int64_t bit_rate;            /* bitrate */
//     int frame_size;              /* frame size */
} AVCodecParameters2;

/* information of one packet of mp4 */
typedef struct MOVIentry2 {
    uint64_t     pos;       /* packet real position in mp4 file */
    int64_t      dts;       /* stts, unit of MOV_TIMESCALE 1000 */
    int64_t      pts;       /* stts + ctts, unit of MOV_TIMESCALE 1000 */
    unsigned int samples_in_chunk; /* the samples number in one chunk */
    unsigned int size;      /* packet size */
    unsigned int chunkNum;  /* Chunk number if the current entry is a chunk start otherwise 0 */
    unsigned int entries;   /* is samples_in_chunk */
    int          cts;       /* dts offset per packet */
/* define for flags */
#define MOV_SYNC_SAMPLE         0x0001
    uint32_t     flags;
//     AVProducerReferenceTime prft; // real time
} MOVIentry2;

/* the video/audio stream struct of mp4 */
typedef struct MOVTrack2 {
    int         entry;          /* total number of packet, increase in mov_write_packet */
    unsigned    timescale;      /* timescale of "mdhd" box, not "mvhd" box */
    uint64_t    time;           /* real time, copy from mov->time */
    int64_t     track_duration; /* track duration time */
    long        sample_count;   /* total count of one chunk packet */
    long        sample_size;    /* if trunk have more packet, length of one packet sample, limit for chunk */
    long        chunkCount;     /* total chunk(packet, cluster) count */
    int         has_keyframes;  /* has key frames */
/* define for flags */
#define MOV_TRACK_CTTS         0x0001
    uint32_t    flags;
    uint16_t    language;       /* for "mdhd" box */
    int         track_id;       /* e.g. 1, 2, */
    int         tag;            /* e.g. stsd fourcc */
    AVCodecParameters2 *par;    /* codec parameter */
    int         max_cluster_num;
    MOVIentry2  *cluster;      /* array entry of all chunk or all packet, parameter per packet */
    int         height;         /* active picture (w/o VBI) height for D-10/IMX */
    int64_t     start_dts;      /* time of the first packet */
    int64_t     start_cts;      /* pts - dts of final packet */
    int64_t     end_pts;        /* time of the final packet */
    int64_t     dts_shift;      /* not used */
    int64_t     data_offset;    /* offset for all chunk */
    void *      priv;           /* pointer of session_unit_t */

//     unsigned    cluster_capacity; /* limited number for cluster */
} MOVTrack2;

/* the mp4 file struct */
typedef struct MOVMuxContext2 {
/* define for mode */
#define MODE_MP4  0x01
    int     mode;           /* is mp4 */
//TODO: modify for set current real time
    int64_t time;           /* real format create time, moov-mvhd-UTC */
    int     nb_streams;     /* number of video/audio/subtitle stream */
    int64_t mdat_pos;       /* video/audio packet position in mp4 file  */
    uint64_t mdat_size;     /* all packets size */
    MOVTrack2 *tracks;      /* streams */
    AVFormatContext2 *fc;   /* universal format structure */
} MOVMuxContext2;

/* "stts" box struct */
typedef struct MOVStts2 {
    unsigned int count; /* count of a duration group  */
    int duration;
} MOVStts2;

/* use global static buffer */
// #define FORMAT_BUFFER_LENGTH (12 * 1024 * 1024)
#define FORMAT_BUFFER_LENGTH (1 * 1024 * 1024)
uint8_t g_fmt_buf[FORMAT_BUFFER_LENGTH];
/* buffer struct for g_fmt_buf */
typedef struct {
    uint8_t *buf;       /* buffer for g_fmt_buf */
    uint8_t unit_size;  /* unit size for buffer segmentation */
    uint32_t maxlen;    /* max buffer length */
    uint32_t curpos;    /* 0 ~ maxlen */
} FMT_BUF_T;

typedef struct
{
    FILE *file;

    AVFormatContext2 format;
    MOVMuxContext2 mov;
//     AVStream2 stream[2];
    MOVTrack2 track[2];
    AVCodecParameters2 par[2];

/* define for movstate */
#define MOV_STATE_HEADER    0
#define MOV_STATE_PACKET    1
#define MOV_STATE_TRAILER   2
    int movstate;
    uint8_t metadata[1024];
    int metalen;
    FMT_BUF_T fmt_buf;
#define CTTS_BUF_SIZE (2048 * sizeof(MOVStts2))
    MOVStts2 *cttsbuf;
    int width;
    int height;
    int start_flag;
} session_unit_t;

//////////////////////////////////////////////////////////////////////////////////////////
static session_unit_t munit;
static session_unit_t *punit = &munit;

/* packet struct for parse */
typedef struct AVPacket2 {
    int64_t pts;
    int64_t dts;
    uint8_t *data;
    int   size;
    int   stream_index;
    int   flags;
    int64_t duration;
    int64_t pos;        /* byte position in stream, -1 if unknown */
} AVPacket2;

/* avio function define */
#define avio_tell ftell
#define avio_seek(pb, pos, whence) fseek((pb), (pos), (whence))

static void avio_write(FILE *s, /*const*/ unsigned char *buf, int size)
{
    fwrite((void *)buf, 1, size, s); // 第三个参数是块个数，返回值是块的个数
}

static void avio_w8(FILE *s, int b)
{
    fwrite((void *)&b, 1, 1, s);
}

static void buf_w8(uint8_t *s, int b)
{
    *s = (uint8_t)b;
}

static void avio_wb16(FILE *s, unsigned int val)
{
    avio_w8(s, (int)val >> 8);
    avio_w8(s, (uint8_t)val);
}

static void buf_wb16(uint8_t *s, unsigned int val)
{
    buf_w8(s++, (int)val >> 8);
    buf_w8(s, (uint8_t)val);
}

static void avio_wb24(FILE *s, unsigned int val)
{
    avio_wb16(s, (int)val >> 8);
    avio_w8(s, (uint8_t)val);
}

/*
static void buf_wb24(uint8_t *s, unsigned int val)
{
    buf_wb16(s, (int)val >> 8);
    s += 2;
    buf_w8(s, (uint8_t)val);
}
*/

static void avio_wl32(FILE *s, unsigned int val)
{
    avio_w8(s, (uint8_t) val       );
    avio_w8(s, (uint8_t)(val >> 8 ));
    avio_w8(s, (uint8_t)(val >> 16));
    avio_w8(s,           val >> 24 );
}

static void avio_wb32(FILE *s, unsigned int val)
{
    avio_w8(s,           val >> 24 );
    avio_w8(s, (uint8_t)(val >> 16));
    avio_w8(s, (uint8_t)(val >> 8 ));
    avio_w8(s, (uint8_t) val       );
}

static void buf_wb32(uint8_t *s, unsigned int val)
{
    buf_w8(s++,           val >> 24 );
    buf_w8(s++, (uint8_t)(val >> 16));
    buf_w8(s++, (uint8_t)(val >> 8 ));
    buf_w8(s++, (uint8_t) val       );
}

static void avio_wb64(FILE *s, uint64_t val)
{
    avio_wb32(s, (uint32_t)(val >> 32));
    avio_wb32(s, (uint32_t)(val & 0xffffffff));
}

static inline void ffio_wfourcc(FILE *pb, const char *s)
{
    avio_wl32(pb, MKTAG(s[0], s[1], s[2], s[3]));
}

static int64_t update_size(FILE *pb, int64_t pos)
{
    int64_t curpos = avio_tell(pb);
    avio_seek(pb, pos, SEEK_SET);
    avio_wb32(pb, curpos - pos); /* rewrite size */
    avio_seek(pb, curpos, SEEK_SET);

    return curpos - pos;
}

static void buf_write(uint8_t *s, const unsigned char *buf, int size)
{
    memcpy(s, (void *)buf, size);
}

/* return 0 or error number */
static int check_pkt(AVFormatContext2 *s, AVPacket2 *pkt)
{
    MOVMuxContext2 *mov = s->priv_data;
    MOVTrack2 *trk = &mov->tracks[pkt->stream_index];
    int64_t ref;        // total duration time, current dts
    uint64_t duration;  // temporary use

    if (trk->entry) {
        /* if not first enter */
        ref = trk->cluster[trk->entry - 1].dts;
    } else if (trk->start_dts != AV_NOPTS_VALUE) {
        /* if not first enter, trk->track_duration is set in mov_write_packet() */
        ref = trk->start_dts + trk->track_duration;
    } else {
        /* if first enter */
        ref = pkt->dts; // skip tests for the first packet
    }

    if (trk->dts_shift != AV_NOPTS_VALUE) {
        /* With negative CTS offsets we have set an offset to the DTS,
         * reverse this for the check. */
        // not enter this
        ref -= trk->dts_shift;
    }

    duration = pkt->dts - ref;
    if (pkt->dts < ref || duration >= INT_MAX) {
        pkt->dts = ref + 1;
        pkt->pts = AV_NOPTS_VALUE;
    }

    //TODO: have mosaic bug
    //TODO: pkt->duration have bug, value is -1 sometimes
//     if (pkt->duration < 0 || pkt->duration > INT_MAX) {
//         return AVERROR(EINVAL);
//     }
    return 0;
}

#define USE_H264 // else HEVC
int parse_sps_pps_vps(uint8_t *idata, int ilen, uint8_t *odata, int *olen)
{
    /* 0x27 or 0x67 is sps, 0x28 or 0x 68 is pps */
    int ret = -1;
//    uint8_t *p = idata;
    *olen = 0;
    int rlen = 0;
    int i;

again:
#ifdef USE_H264
    if ((idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x00 && idata[3] == 0x01 && idata[4] == 0x27)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x00 && idata[3] == 0x01 && idata[4] == 0x28)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x00 && idata[3] == 0x01 && idata[4] == 0x67)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x00 && idata[3] == 0x01 && idata[4] == 0x68)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x01 && idata[3] == 0x27)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x01 && idata[3] == 0x28)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x01 && idata[3] == 0x67)
            || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x01 && idata[3] == 0x68)) {
        ret = 0;
        for (i = 0; i <= 5; i++) {
            *odata++ = *idata++;
            *olen = *olen + 1;
            rlen++;
        }
        while (rlen < ilen) {
            if (rlen < ilen - 4) {
                if ((idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x00 && idata[3] == 0x01)
                    || (idata[0] == 0x00 && idata[1] == 0x00 && idata[2] == 0x01)) {
                    goto again;
                } else {
                    *odata++ = *idata++;
                    *olen = *olen + 1;
                    rlen++;
                }
            } else {
                *odata++ = *idata++;
                *olen = *olen + 1;
                rlen++;
            }
        }
    } else {
    }
#else
    // reserve for HEVC
#endif

    return ret;
}

/* default only support h264 */
static int mov_write_ftyp_tag(FILE *pb, AVFormatContext2 *s)
{
//    MOVMuxContext2 *mov = (MOVMuxContext2 *)s->priv_data;

    int64_t pos = avio_tell(pb);

    avio_wb32(pb, 32); /* size */

    ffio_wfourcc(pb, "ftyp");

    /* set flag in mov_init() */
    ffio_wfourcc(pb, "isom");   /* write major brand: "ison" - MODE_MP4 */
    int minor = 0x200;          /* 512, default MODE_MP4 value, MODE_3GP: 0x100/0x200, MODE_3G2: 0x10000/0x20000 */
    avio_wb32(pb, minor);

    ffio_wfourcc(pb, "isom"); /* is NEGATIVE_CTS_OFFSETS */
    ffio_wfourcc(pb, "iso2"); /* not DEFAULT_BASE_MOOF */
    ffio_wfourcc(pb, "avc1"); /* is h264 */
    ffio_wfourcc(pb, "mp41"); /* is mp4 */

    return update_size(pb, pos);
}

static int mov_write_mdat_tag(FILE *pb, AVFormatContext2 *s)
{
    MOVMuxContext2 *mov = (MOVMuxContext2 *)s->priv_data;

    avio_wb32(pb, 8);   /* placeholder for extended size field (64 bit)*/
    ffio_wfourcc(pb, "free");

    mov->mdat_pos = avio_tell(pb);
    avio_wb32(pb, 0);   /* size placeholder*/
    ffio_wfourcc(pb, "mdat");

    return 0;
}

/* transformation matrix
     |a  b  u|
     |c  d  v|
     |tx ty w| */
static void write_matrix(FILE *pb, int16_t a, int16_t b, int16_t c,
                         int16_t d, int16_t tx, int16_t ty)
{
    avio_wb32(pb, a << 16);  /* 16.16 format */
    avio_wb32(pb, b << 16);  /* 16.16 format */
    avio_wb32(pb, 0);        /* u in 2.30 format */
    avio_wb32(pb, c << 16);  /* 16.16 format */
    avio_wb32(pb, d << 16);  /* 16.16 format */
    avio_wb32(pb, 0);        /* v in 2.30 format */
    avio_wb32(pb, tx << 16); /* 16.16 format */
    avio_wb32(pb, ty << 16); /* 16.16 format */
    avio_wb32(pb, 1 << 30);  /* w in 2.30 format */
}

/* get total trunk duration time */
static int64_t calc_pts_duration(MOVMuxContext2 *mov, MOVTrack2 *track)
{
    if (track->end_pts != AV_NOPTS_VALUE &&
            track->start_dts != AV_NOPTS_VALUE &&
            track->start_cts != AV_NOPTS_VALUE) {
        return track->end_pts - (track->start_dts + track->start_cts);
    }
    return track->track_duration;
}

/* timescale from c to b */
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c/*, enum AVRounding rnd*/)
{
    return (a * b) / c;
}

static int mov_write_mvhd_tag(FILE *pb, MOVMuxContext2 *mov)
{
    int max_track_id = 1, i;    // current track id
    int64_t max_track_len = 0;  // durations(ms)
    int version;

    for (i = 0; i < mov->nb_streams; i++) {
        /* entry increase in mov_write_packet, timescale set in mov_init */
        if (mov->tracks[i].entry > 0 && mov->tracks[i].timescale) {
            /* unit is time_scale, from "mdhd" 12800 to mvhd 1000 */
            int64_t max_track_len_temp = av_rescale_rnd(
                                                calc_pts_duration(mov, &mov->tracks[i]),
                                                MOV_TIMESCALE,
                                                mov->tracks[i].timescale
                                                );
            if (max_track_len < max_track_len_temp)
                max_track_len = max_track_len_temp;     // durations(ms)
            if (max_track_id < mov->tracks[i].track_id)
                max_track_id = mov->tracks[i].track_id; // current track id
        }
    }

    version = 0; // if track len > 4.3G, version is 1, not support
    avio_wb32(pb, version == 1 ? 120 : 108); /* size */
    ffio_wfourcc(pb, "mvhd");
    avio_w8(pb, version);
    avio_wb24(pb, 0); /* flags */
    if (version == 1) {
    } else {
        avio_wb32(pb, mov->time); /* creation time */ // 0X7C25B080
        avio_wb32(pb, mov->time); /* modification time */
    }
    avio_wb32(pb, MOV_TIMESCALE);
    // change timescale from 12800 to 1000
    (version == 1) ? : avio_wb32(pb, max_track_len); /* duration of longest track */

    avio_wb32(pb, 0x00010000); /* reserved (preferred rate) 1.0 = normal */
    avio_wb16(pb, 0x0100); /* reserved (preferred volume) 1.0 = normal */
    avio_wb16(pb, 0); /* reserved */
    avio_wb32(pb, 0); /* reserved */
    avio_wb32(pb, 0); /* reserved */

    /* Matrix structure */
    write_matrix(pb, 1, 0, 0, 1, 0, 0);

    avio_wb32(pb, 0); /* reserved (preview time) */
    avio_wb32(pb, 0); /* reserved (preview duration) */
    avio_wb32(pb, 0); /* reserved (poster time) */
    avio_wb32(pb, 0); /* reserved (selection time) */
    avio_wb32(pb, 0); /* reserved (selection duration) */
    avio_wb32(pb, 0); /* reserved (current time) */
    avio_wb32(pb, max_track_id + 1); /* Next track id */
    return 0x6c;
}

#define MOV_TKHD_FLAG_ENABLED       0x0001
#define MOV_TKHD_FLAG_IN_MOVIE      0x0002

static int mov_write_tkhd_tag(FIL *pb, MOVMuxContext2 *mov,
                              MOVTrack2 *track/*, AVStream2 *st*/)
{
    int64_t duration = av_rescale_rnd(calc_pts_duration(mov, track),
                                      MOV_TIMESCALE, track->timescale);
    int version = 0; // not support version 1: duration > INT32_MAX
    int flags   = MOV_TKHD_FLAG_IN_MOVIE; // "tkhd" tag
    int group   = 0; // alternate grou, don't care

    flags |= MOV_TKHD_FLAG_ENABLED;
    (version == 1) ? avio_wb32(pb, 104) : avio_wb32(pb, 92); /* size */
    ffio_wfourcc(pb, "tkhd");
    avio_w8(pb, version);
    avio_wb24(pb, flags);

    // set track->time in mov_write_moov_tag()
    avio_wb32(pb, track->time); /* creation time */ //0X7C25B080 2036/01/02 00:00:00
    avio_wb32(pb, track->time); /* modification time */ // 0X7C25B080

    avio_wb32(pb, track->track_id); /* track-id */
    avio_wb32(pb, 0); /* reserved */

    avio_wb32(pb, duration); // duration unit is 1000, "mvhd" timescale

    avio_wb32(pb, 0); /* reserved */
    avio_wb32(pb, 0); /* reserved */
    avio_wb16(pb, 0); /* layer */
    avio_wb16(pb, group); /* alternate group) */

    /* Volume, only for audio */
     if (track->par->codec_type == AVMEDIA_TYPE_AUDIO2)
         avio_wb16(pb, 0x0100);
     else
        avio_wb16(pb, 0);
    avio_wb16(pb, 0); /* reserved */

    /* Matrix structure */
    // rotate
    write_matrix(pb,  1,  0,  0,  1, 0, 0);

    /* Track width and height, for visual only */
    if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2) {
        avio_wb32(pb, track->par->width * 0x10000ULL);
        avio_wb32(pb, track->height * 0x10000U);
    } else {
        // if audio
        avio_wb32(pb, 0);
        avio_wb32(pb, 0);
    }
    return 0x5c;
}

static int mov_write_mdhd_tag(FIL *pb, MOVMuxContext2 *mov,
                              MOVTrack2 *track)
{
    int64_t duration = calc_pts_duration(mov, track);
    // version only support 0
    int version = 0;//duration < INT32_MAX ? 0 : 1;

    (version == 1) ? avio_wb32(pb, 44) : avio_wb32(pb, 32); /* size */
    ffio_wfourcc(pb, "mdhd");
    avio_w8(pb, version);
    avio_wb24(pb, 0); /* flags */
    avio_wb32(pb, track->time); /* creation time */ // 0X7C25B080 2036/01/02 00:00:00
    avio_wb32(pb, track->time); /* modification time */ // 0X7C25B080

    avio_wb32(pb, track->timescale); /* time scale (sample rate for audio) */
    if (!track->entry)
        avio_wb32(pb, 0);
    else {
        avio_wb32(pb, duration); /* duration */
    }

    avio_wb16(pb, track->language); /* language */
    avio_wb16(pb, 0); /* reserved (quality) */

    return 32;
}

static int mov_write_hdlr_tag(AVFormatContext2 *s, FIL *pb, MOVTrack2 *track)
{
//    MOVMuxContext2 *mov = s->priv_data;
    const char *hdlr, *descr = NULL, *hdlr_type = NULL;
    int64_t pos = avio_tell(pb);

    hdlr      = "dhlr";
    hdlr_type = "url ";
    descr     = "DataHandler";

    if (track) {
        hdlr = "\0\0\0\0";
        if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2) {
            hdlr_type = "vide";
            descr     = "VideoHandler";
        } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO2) {
            hdlr_type = "soun";
            descr     = "SoundHandler";
        } else {
            // error
        }
    }

    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "hdlr");
    avio_wb32(pb, 0); /* Version & flags */
    avio_write(pb, (unsigned char *)hdlr, 4); /* handler */
    ffio_wfourcc(pb, hdlr_type); /* handler type */
    avio_wb32(pb, 0); /* reserved */
    avio_wb32(pb, 0); /* reserved */
    avio_wb32(pb, 0); /* reserved */
    avio_write(pb, (unsigned char *)descr, strlen(descr)); /* handler description */
    if (track)
        avio_w8(pb, 0); /* c string */
    return update_size(pb, pos);
}

static int mov_write_smhd_tag(FIL *pb)
{
    avio_wb32(pb, 16); /* size */
    ffio_wfourcc(pb, "smhd");
    avio_wb32(pb, 0); /* version & flags */
    avio_wb16(pb, 0); /* reserved (balance, normally = 0) */
    avio_wb16(pb, 0); /* reserved */
    return 16;
}

static int mov_write_vmhd_tag(FIL *pb)
{
    avio_wb32(pb, 0x14); /* size (always 0x14) */
    ffio_wfourcc(pb, "vmhd");
    avio_wb32(pb, 0x01); /* version & flags */
    avio_wb64(pb, 0); /* reserved (graphics mode = copy) */
    return 0x14;
}

static int mov_write_dref_tag(FIL *pb)
{
    avio_wb32(pb, 28); /* size */
    ffio_wfourcc(pb, "dref");
    avio_wb32(pb, 0); /* version & flags */
    avio_wb32(pb, 1); /* entry count */

    avio_wb32(pb, 0xc); /* size */
    ffio_wfourcc(pb, "url ");
    avio_wb32(pb, 1); /* version & flags */

    return 28;
}

static int mov_write_dinf_tag(FIL *pb)
{
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "dinf");
    mov_write_dref_tag(pb);
    return update_size(pb, pos);
}

static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p, const uint8_t *end)
{
    const uint8_t *a = p + 4 - ((int)p & 3);

    for (end -= 3; p < a && p < end; p++) {
        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
            return p;
    }

    for (end -= 3; p < end; p += 4) {
        uint32_t x = *(const uint32_t*)p;
//      if ((x - 0x01000100) & (~x) & 0x80008000) // little endian
//      if ((x - 0x00010001) & (~x) & 0x00800080) // big endian
        if ((x - 0x01010101) & (~x) & 0x80808080) { // generic
            if (p[1] == 0) {
                if (p[0] == 0 && p[2] == 1)
                    return p;
                if (p[2] == 0 && p[3] == 1)
                    return p+1;
            }
            if (p[3] == 0) {
                if (p[2] == 0 && p[4] == 1)
                    return p+2;
                if (p[4] == 0 && p[5] == 1)
                    return p+3;
            }
        }
    }

    for (end += 3; p < end; p++) {
        if (p[0] == 0 && p[1] == 0 && p[2] == 1)
            return p;
    }

    return end + 3;
}

const uint8_t *ff_avc_find_startcode(const uint8_t *p, const uint8_t *end){
    const uint8_t *out= ff_avc_find_startcode_internal(p, end);
    if(p<out && out<end && !out[-1]) out--;
    return out;
}

int ff_avc_parse_nal_units(uint8_t *buf, const uint8_t *buf_in, int size)
{
    const uint8_t *p = buf_in;
    const uint8_t *end = p + size;
    const uint8_t *nal_start, *nal_end;

    size = 0;
    nal_start = ff_avc_find_startcode(p, end);
    for (;;) {
        while (nal_start < end && !*(nal_start++));
        if (nal_start == end)
            break;

        nal_end = ff_avc_find_startcode(nal_start, end);
        uint32_t tmplen =  nal_end - nal_start;
        buf_wb32(buf, tmplen);

        buf += sizeof(uint32_t);
        buf_write(buf, nal_start, nal_end - nal_start);

        buf += nal_end - nal_start;
        size += 4 + nal_end - nal_start;
        nal_start = nal_end;
    }
    return size;
}

#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
// 7.4.2.1.1: seq_parameter_set_id is in [0, 31].
#define H264_MAX_SPS_COUNT 32
// 7.4.2.2: pic_parameter_set_id is in [0, 255].
#define H264_MAX_PPS_COUNT 256

int ff_isom_write_avcc(FIL *pb, const uint8_t *data, int len)
{
    uint8_t start[128], *end, *buf;
    uint8_t sps[128], pps[64], sps_ext[32];
    uint8_t *sps_pb = sps, *pps_pb = pps, *sps_ext_pb = sps_ext;
    uint32_t sps_size = 0, pps_size = 0, sps_ext_size = 0;
    int ret, nb_sps = 0, nb_pps = 0, nb_sps_ext = 0;
    int i;

    buf = start;

    if (len <= 6)
        return -1;

    ret = ff_avc_parse_nal_units(buf, data, len);
    if (ret < 0)
        return ret;

    end = buf + len;

    /* look for sps and pps */
    while (end - buf > 4) {
        uint32_t size;
        uint8_t nal_type;
        uint32_t tmp32 = 0;
        for (i = 0; i < sizeof(uint32_t); i++) { // fix a bug of *(uint32_t *)buf
            tmp32 <<= 8;
            tmp32 |= buf[i];
        }

        size = FFMIN(tmp32, end - buf - 4);
        buf += 4;
        nal_type = buf[0] & 0x1f;

        if (nal_type == 7) { /* SPS */
            nb_sps++;
// #define UINT16_MAX (65535)
            if (size > UINT16_MAX || nb_sps >= H264_MAX_SPS_COUNT) {
                ret = -1;
                goto fail;
            }
            buf_wb16(sps_pb, size);
            sps_pb += 2;
            buf_write(sps_pb, buf, size);
            sps_pb += size;
        } else if (nal_type == 8) { /* PPS */
            nb_pps++;
            if (size > UINT16_MAX || nb_pps >= H264_MAX_PPS_COUNT) {
                ret = -1;
                goto fail;
            }
            buf_wb16(pps_pb, size);
            pps_pb += 2;
            buf_write(pps_pb, buf, size);
            pps_pb += size;
        } else if (nal_type == 13) { /* SPS_EXT */
            nb_sps_ext++;
            if (size > UINT16_MAX || nb_sps_ext >= 256) {
                ret = -1;
                goto fail;
            }
            buf_wb16(sps_ext_pb, size);
            sps_ext_pb += 2;
            buf_write(sps_ext_pb, buf, size);
            sps_ext_pb += size;
        }

        buf += size;
    }
    sps_size = sps_pb - sps;//avio_get_dyn_buf(sps_pb, &sps);
    pps_size = pps_pb - pps;//avio_get_dyn_buf(pps_pb, &pps);
    sps_ext_size = sps_ext_pb - sps_ext;//avio_get_dyn_buf(sps_ext_pb, &sps_ext);

    if (sps_size < 6 || !pps_size) {
        ret = -1;
        goto fail;
    }

    avio_w8(pb, 1); /* version */
    avio_w8(pb, sps[3]); /* profile */
    avio_w8(pb, sps[4]); /* profile compat */
    avio_w8(pb, sps[5]); /* level */
    avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
    avio_w8(pb, 0xe0 | nb_sps); /* 3 bits reserved (111) + 5 bits number of sps */

    avio_write(pb, sps, sps_size);
    avio_w8(pb, nb_pps); /* number of pps */
    avio_write(pb, pps, pps_size);

    if (sps[3] != 66 && sps[3] != 77 && sps[3] != 88)
    {
    }

fail:

    return ret;
}

/**
 * @see https://blog.csdn.net/vincent_blog/article/details/46517733
 * @see https://www.cnblogs.com/liyuanhong/articles/14155993.html
 * x264_encoder_headers() from x264
 */
static int mov_write_avcc_tag(FIL *pb, MOVTrack2 *track)
{
    session_unit_t *punit = track->priv;
    int64_t pos = avio_tell(pb);

    avio_wb32(pb, 0);
    ffio_wfourcc(pb, "avcC");
    ff_isom_write_avcc(pb, punit->metadata, punit->metalen);
    return update_size(pb, pos);
}

/* "avc1" tag */
static int mov_write_video_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    char compressor_name[32] = { 0 };
//    int avid = 0;

    int uncompressed_ycbcr = 0;

    avio_wb32(pb, 0); /* size */
#if 0
    avio_wl32(pb, track->tag); // store it byteswapped
#else
    ffio_wfourcc(pb, "avc1"); // for default
#endif
    avio_wb32(pb, 0); /* Reserved */
    avio_wb16(pb, 0); /* Reserved */
    avio_wb16(pb, 1); /* Data-reference index */

    if (uncompressed_ycbcr) {
        avio_wb16(pb, 2); /* Codec stream version */
    } else {
        avio_wb16(pb, 0); /* Codec stream version */
    }
    avio_wb16(pb, 0); /* Codec stream revision (=0) */

    avio_wb32(pb, 0); /* Reserved */
    avio_wb32(pb, 0); /* Reserved */
    avio_wb32(pb, 0); /* Reserved */

    avio_wb16(pb, track->par->width); /* Video width */
    avio_wb16(pb, track->height); /* Video height */
    avio_wb32(pb, 0x00480000); /* Horizontal resolution 72dpi */
    avio_wb32(pb, 0x00480000); /* Vertical resolution 72dpi */
    avio_wb32(pb, 0); /* Data size (= 0) */
    avio_wb16(pb, 1); /* Frame count (= 1) */

    /* FIXME not sure, ISO 14496-1 draft where it shall be set to 0 */
    avio_w8(pb, 0);
    avio_write(pb, (unsigned char *)compressor_name, 31);

    avio_wb16(pb, 0x18); /* Reserved */
    avio_wb16(pb, 0xffff); /* Reserved */

    //TODO: add for hevc
    // if (track->par->codec_id == AV_CODEC_ID_HEVC)
    //     mov_write_hvcc_tag(pb, track);
    // } else if (track->par->codec_id == AV_CODEC_ID_H264) {
        mov_write_avcc_tag(pb, track);
    // }

    return update_size(pb, pos);
}

static void put_descr(FIL *pb, int tag, unsigned int size)
{
    int i = 3;
    avio_w8(pb, tag);
    for (; i > 0; i--)
        avio_w8(pb, (size >> (7 * i)) | 0x80);
    avio_w8(pb, size & 0x7F);
}

//[MPEG-4 Audio 关于esds 记录](http://www.360doc.com/content/18/0329/01/12109864_741134149.shtml)
//[mp4 atom - 如何区分音频编解码器？是AAC还是MP3？](https://www.orcode.com/question/972887_kbc764.html)
// mp3和aac都是mp4a and esds，需要在esds中分辨
//TODO: add avg_bitrate
static int mov_write_esds_tag(FIL *pb, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int decoder_specific_info_len = 0;//track->vos_len ? 5 + track->vos_len : 0;
//    unsigned avg_bitrate;

    avio_wb32(pb, 0); // size
    ffio_wfourcc(pb, "esds");
    avio_wb32(pb, 0); // Version

    // ES descriptor
    put_descr(pb, 0x03, 3 + 5+13 + decoder_specific_info_len + 5+1);

    avio_wb16(pb, track->track_id);
    avio_w8(pb, 0x00); // flags (= no flags)

    // DecoderConfig descriptor
    put_descr(pb, 0x04, 13 + decoder_specific_info_len);

    // Object type indication
#ifdef AUDIO_USE_AAC
    avio_w8(pb, 0x67); // aac lc
#else
    avio_w8(pb, 0x6B); // mp3
#endif

    // the following fields is made of 6 bits to identify the streamtype (4 for video, 5 for audio)
    // plus 1 bit to indicate upstream and 1 bit set to 1 (reserved)
    if (track->par->codec_type == AVMEDIA_TYPE_AUDIO2)
        avio_w8(pb, 0x15); // flags (= Audiostream)

    avio_wb24(pb, 0/*props ? props->buffer_size / 8 : 0*/); // Buffersize DB

    //TODO: modify for avg_bitrate
    // avg_bitrate = compute_avg_bitrate(track);
    avio_wb32(pb, 128008/*props ? FFMAX3(props->max_bitrate, props->avg_bitrate, avg_bitrate) : FFMAX(track->par->bit_rate, avg_bitrate)*/); // maxbitrate (FIXME should be max rate in any 1 sec window)
    avio_wb32(pb, 128008/*avg_bitrate*/);

    // SL descriptor
    put_descr(pb, 0x06, 1);
    avio_w8(pb, 0x02);
    return update_size(pb, pos);
}

/* "mp4a" tag */
// mp3和aac都是mp4a and esds，需要在esds中分辨
static int mov_write_audio_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int version = 0;
    uint32_t tag = track->tag = MKTAG('m','p','4','a');
    int ret = 0;

    avio_wb32(pb, 0); /* size */
    avio_wl32(pb, tag); // store it byteswapped

    avio_wb32(pb, 0); /* Reserved */
    avio_wb16(pb, 0); /* Reserved */
    avio_wb16(pb, 1); /* Data-reference index, XXX  == 1 */

    /* SoundDescription */
    avio_wb16(pb, version); /* Version */
    avio_wb16(pb, 0); /* Revision level */
    avio_wb32(pb, 0); /* Reserved */

    { /* reserved for mp4/3gp */
        avio_wb16(pb, 2);
        avio_wb16(pb, 16);
        avio_wb16(pb, 0);
    }

    avio_wb16(pb, 0); /* packet size (= 0) */
    avio_wb16(pb, track->par->sample_rate);

    avio_wb16(pb, 0); /* Reserved */

    if (track->tag == MKTAG('m','p','4','a'))
        ret = mov_write_esds_tag(pb, track);

    if (ret < 0)
        return ret;

    ret = update_size(pb, pos);
    return ret;
}

/* sample description box */
static int mov_write_stsd_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int ret = 0;
    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "stsd");
    avio_wb32(pb, 0); /* version & flags */
    avio_wb32(pb, 1); /* entry count */
    /* "avc1" tag */
     if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2)
        ret = mov_write_video_tag(s, pb, mov, track);
    /* "mp4a" tag */
     else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO2)
         ret = mov_write_audio_tag(s, pb, mov, track);

    if (ret < 0)
        return ret;

    return update_size(pb, pos);
}

//TODO: add ctts value
//TODO: add pts-->dts(e.g. 512 1024 2560 0)
static int mov_write_ctts_tag(AVFormatContext2 *s, FIL *pb, MOVTrack2 *track)
{
    session_unit_t *punit = track->priv;
//    MOVMuxContext2 *mov = s->priv_data;
    MOVStts2 *ctts_entries;
    uint32_t entries = 0;
    uint32_t atom_size;
    int i;

    ctts_entries = punit->cttsbuf;//av_malloc_array((track->entry + 1), sizeof(*ctts_entries)); /* worst case */
    if (!ctts_entries)
        return -1;//AVERROR(ENOMEM);
    ctts_entries[0].count = 1;
    ctts_entries[0].duration = 0;//track->cluster[0].cts;
    for (i = 1; i < track->entry; i++) {
        //TODO: add for ctts
        // if (track->cluster[i].cts == ctts_entries[entries].duration) {
            ctts_entries[entries].count++; /* compress */
        // } else {
        //     entries++;
        //     ctts_entries[entries].duration = track->cluster[i].cts;
        //     ctts_entries[entries].count = 1;
        // }
    }
    entries++; /* last one */
    atom_size = 16 + (entries * 8);
    avio_wb32(pb, atom_size); /* size */
    ffio_wfourcc(pb, "ctts");
    avio_w8(pb, 0); /* version */
    avio_wb24(pb, 0); /* flags */
    avio_wb32(pb, entries); /* entry count */
    for (i = 0; i < entries; i++) {
        avio_wb32(pb, ctts_entries[i].count);
        avio_wb32(pb, ctts_entries[i].duration);
    }
    return atom_size;
}

/* packet duration, unit is same as pts and dts */
static int get_cluster_duration(MOVTrack2 *track, int cluster_idx)
{
    int64_t next_dts;

    // cluster_idx is per packet number
    if (cluster_idx >= track->entry)
        return 0;

    if (cluster_idx + 1 == track->entry) {
        next_dts = track->track_duration + track->start_dts; // the last of packet
    }
    else {
        next_dts = track->cluster[cluster_idx + 1].dts; // normal use this
    }

    /* dts'' - dts' */
    next_dts -= track->cluster[cluster_idx].dts; // dts'' - dts'


    return next_dts;
}

/* Time to sample atom */
static int mov_write_stts_tag(FIL *pb, MOVTrack2 *track)
{
    MOVStts2 tmp_stts_entries[10]; //TODO: ALLOC IN GLOBAL BUFFER
    MOVStts2 *stts_entries = tmp_stts_entries;
    uint32_t entries = -1;
    uint32_t atom_size;
    int i;

    if (0) {
        stts_entries[0].count = track->sample_count;
        stts_entries[0].duration = 1;
        entries = 1;

        if (track->track_id == 1) {
            // for video default
            stts_entries[0].duration = 512;
        } else {
            // for audio default
            stts_entries[0].duration = 1152;
        }
    } else {
        if (track->entry) { // will enter
        }
        /* foreach all packets */
        for (i = 0; i < track->entry; i++) {
            int duration = get_cluster_duration(track, i);
            if ((i && duration == stts_entries[entries].duration) || duration == 0) {
                // loop in this
                stts_entries[entries].count++; /* compress */
            } else {
                // should enter once only
                entries++; // entries = 0;
                stts_entries[entries].duration = duration;
                stts_entries[entries].count = 1;
            }
        }
        entries++; /* last one */ // entries = 1
    }
    atom_size = 16 + (entries * 8);
    avio_wb32(pb, atom_size); /* size */
    ffio_wfourcc(pb, "stts");
    avio_wb32(pb, 0); /* version & flags */
    avio_wb32(pb, entries); /* entry count */
    for (i = 0; i < entries; i++) {
        // entries hould is 1
        avio_wb32(pb, stts_entries[i].count);   /* sample count, total packet count */
        avio_wb32(pb, stts_entries[i].duration);/* sample delta */
    }

    return atom_size;
}

/* Sample to chunk atom, default one chunk is one sample(packet) */
static int mov_write_stsc_tag(FIL *pb, MOVTrack2 *track)
{
    int index = 0, oldval = -1, i;
    int64_t entryPos, curpos;

    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "stsc");
    avio_wb32(pb, 0); // version & flags
    entryPos = avio_tell(pb);
    avio_wb32(pb, track->chunkCount); // entry count

    /* foreach all packets */
    for (i = 0; i < track->entry; i++) {
        if (oldval != track->cluster[i].samples_in_chunk && track->cluster[i].chunkNum) {
            avio_wb32(pb, track->cluster[i].chunkNum); // first chunk
            avio_wb32(pb, track->cluster[i].samples_in_chunk); // samples per chunk
            avio_wb32(pb, 0x1); // sample description index
            oldval = track->cluster[i].samples_in_chunk;
            index++;
        }
    }
    curpos = avio_tell(pb);
    avio_seek(pb, entryPos, SEEK_SET);
    avio_wb32(pb, index); // rewrite size
    avio_seek(pb, curpos, SEEK_SET);

    return update_size(pb, pos);
}

/* Sample size atom */
static int mov_write_stsz_tag(FIL *pb, MOVTrack2 *track)
{
    int equalChunks = 1;
    int i, j, entries = 0, tst = -1, oldtst = -1;

    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "stsz");
    avio_wb32(pb, 0); /* version & flags */

    /* foreach all chunks */
    for (i = 0; i < track->entry; i++) {
        tst = track->cluster[i].size / track->cluster[i].entries; // pktsize per samples_to_chunk
        if (oldtst != -1 && tst != oldtst)
            equalChunks = 0;
        oldtst = tst;
        entries += track->cluster[i].entries;
    }
    if (equalChunks && track->entry) {
        int sSize = track->entry ? track->cluster[0].size / track->cluster[0].entries : 0;
        sSize = FFMAX(1, sSize); // adpcm mono case could make sSize == 0
        avio_wb32(pb, sSize); // sample size
        avio_wb32(pb, entries); // sample count
    /* foreach all packets */
    } else {
        avio_wb32(pb, 0); // sample size
        avio_wb32(pb, entries); // sample count
        for (i = 0; i < track->entry; i++) {
            for (j = 0; j < track->cluster[i].entries; j++) {
                avio_wb32(pb, track->cluster[i].size /
                          track->cluster[i].entries);
            }
        }
    }
    return update_size(pb, pos);
}

/* Chunk offset atom */
static int mov_write_stco_tag(FIL *pb, MOVTrack2 *track)
{
    int i;
    int mode64 = 0;//co64_required(track); // use 32 bit size variant if possible
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */
    if (mode64)
        ffio_wfourcc(pb, "co64");
    else
        ffio_wfourcc(pb, "stco");
    avio_wb32(pb, 0); /* version & flags */
    avio_wb32(pb, track->chunkCount); /* entry count */

    for (i = 0; i < track->entry; i++) {
        if (track->cluster[i].chunkNum == 0)
            continue;
        if (mode64 == 1)
            avio_wb64(pb, track->cluster[i].pos + track->data_offset);
        else
            avio_wb32(pb, track->cluster[i].pos + track->data_offset);
    }
    return update_size(pb, pos);
}

/* Sync sample atom */
static int mov_write_stss_tag(FIL *pb, MOVTrack2 *track, uint32_t flag)
{
    int64_t curpos, entryPos;
    int i, index = 0;
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); // size
    ffio_wfourcc(pb, flag == MOV_SYNC_SAMPLE ? "stss" : "stps");
    avio_wb32(pb, 0); // version & flags
    entryPos = avio_tell(pb);
    avio_wb32(pb, track->entry); // entry count
    for (i = 0; i < track->entry; i++) {
        if (track->cluster[i].flags & flag) {
            avio_wb32(pb, i + 1);
            index++;
        }
    }
    curpos = avio_tell(pb);
    avio_seek(pb, entryPos, SEEK_SET);
    avio_wb32(pb, index); // rewrite size
    avio_seek(pb, curpos, SEEK_SET);
    return update_size(pb, pos);
}

static int mov_write_stbl_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int ret = 0;

    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "stbl");

    /* Sample Description Box */
    if ((ret = mov_write_stsd_tag(s, pb, mov, track)) < 0)
        return ret;

    /* Decoding Time to Sample Box */
    mov_write_stts_tag(pb, track);

    /* Sync Sample Box(key packet number) */
    if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2)/* && track->has_keyframes && track->has_keyframes < track->entry*/
        mov_write_stss_tag(pb, track, MOV_SYNC_SAMPLE);

    if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2) {
        if ((ret = mov_write_ctts_tag(s, pb, track)) < 0)
            return ret;
    }

    /* Sample To Chunk Box */
    mov_write_stsc_tag(pb, track); //1 1 1 1
    mov_write_stsz_tag(pb, track);
    mov_write_stco_tag(pb, track);
    return update_size(pb, pos);
}

static int mov_write_minf_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int ret;

    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "minf");
    if (track->par->codec_type == AVMEDIA_TYPE_VIDEO2)
        mov_write_vmhd_tag(pb);
    else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO2)
        mov_write_smhd_tag(pb);
    mov_write_dinf_tag(pb);

    if ((ret = mov_write_stbl_tag(s, pb, mov, track)) < 0)
        return ret;
    return update_size(pb, pos);
}

static int mov_write_mdia_tag(AVFormatContext2 *s, FIL *pb,
                              MOVMuxContext2 *mov, MOVTrack2 *track)
{
    int64_t pos = avio_tell(pb);
    int ret;

    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "mdia");
    mov_write_mdhd_tag(pb, mov, track);
    mov_write_hdlr_tag(s, pb, track);

    if ((ret = mov_write_minf_tag(s, pb, mov, track)) < 0)
        return ret;
    return update_size(pb, pos);
}

/* This box seems important for the psp playback ... without it the movie seems to hang */
static int mov_write_edts_tag(FIL *pb, MOVMuxContext2 *mov,
                              MOVTrack2 *track)
{
    int64_t duration = av_rescale_rnd(calc_pts_duration(mov, track),
                                      MOV_TIMESCALE, track->timescale);
    int version = duration < INT32_MAX ? 0 : 1;
    int entry_size, entry_count, size;
    int64_t delay, start_ct = track->start_cts;
    int64_t start_dts = track->start_dts;

    if (track->entry) {
        if (start_dts != track->cluster[0].dts || start_ct != track->cluster[0].cts) {
            start_dts = track->cluster[0].dts;
            start_ct  = track->cluster[0].cts;
        }
    }

    delay = 0;//av_rescale_rnd(start_dts + start_ct, MOV_TIMESCALE, track->timescale);
    version |= delay < INT32_MAX ? 0 : 1;

    entry_size = (version == 1) ? 20 : 12;
    entry_count = 1 + (delay > 0);
    size = 24 + entry_count * entry_size;

    /* write the atom data */
    avio_wb32(pb, size);
    ffio_wfourcc(pb, "edts");
    avio_wb32(pb, size - 8);
    ffio_wfourcc(pb, "elst");
    avio_w8(pb, version);
    avio_wb24(pb, 0); /* flags */

    avio_wb32(pb, entry_count); // default entry count is 1
    if (delay > 0) { /* add an empty edit to delay presentation */
        /* In the positive delay case, the delay includes the cts
         * offset, and the second edit list entry below trims out
         * the same amount from the actual content. This makes sure
         * that the offset last sample is included in the edit
         * list duration as well. */
        if (version == 1) {
        } else {
            avio_wb32(pb, delay);
            avio_wb32(pb, -1);
        }
        avio_wb32(pb, 0x00010000);
    } else {
        /* Avoid accidentally ending up with start_ct = -1 which has got a
         * special meaning. Normally start_ct should end up positive or zero
         * here, but use FFMIN in case dts is a small positive integer
         * rounded to 0 when represented in MOV_TIMESCALE units. */
        start_ct  = -FFMIN(start_dts, 0);
        /* Note, this delay is calculated from the pts of the first sample,
         * ensuring that we don't reduce the duration for cases with
         * dts<0 pts=0. */
        duration += delay;
    }

    /* For fragmented files, we don't know the full length yet. Setting
     * duration to 0 allows us to only specify the offset, including
     * the rest of the content (from all future fragments) without specifying
     * an explicit duration. */

    /* duration */
    if (version == 1) {
    } else {
        avio_wb32(pb, duration); // write segment duration
        avio_wb32(pb, start_ct); // write media time
    }
    avio_wb32(pb, 0x00010000); // write media rate integer 1 and media rate fraction 0
    return size;
}

static int mov_write_trak_tag(AVFormatContext2 *s, FIL *pb, MOVMuxContext2 *mov,
                              MOVTrack2 *track/*, AVStream2 *st*/)
{
    int64_t pos = avio_tell(pb);
    int ret;

    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "trak");

    mov_write_tkhd_tag(pb, mov, track/*, st*/);
    mov_write_edts_tag(pb, mov, track);  // PSP Movies and several other cases require edts box

    if ((ret = mov_write_mdia_tag(s, pb, mov, track)) < 0)
        return ret;

    return update_size(pb, pos);
}

/* helper function to write a data tag with the specified string as data */
static int mov_write_string_data_tag(FIL *pb, const char *data, int lang, int long_style)
{
    if (long_style) {
        int size = 16 + strlen(data);
        avio_wb32(pb, size); /* size */
        ffio_wfourcc(pb, "data");
        avio_wb32(pb, 1);
        avio_wb32(pb, 0);
        avio_write(pb, (unsigned char *)data, strlen(data));
        return size;
    } else {
        if (!lang)
            lang = 0;//ff_mov_iso639_to_lang("und", 1);
        avio_wb16(pb, strlen(data)); /* string length */
        avio_wb16(pb, lang);
        avio_write(pb, (unsigned char *)data, strlen(data));
        return strlen(data) + 4;
    }
}

static int mov_write_string_tag(FIL *pb, const char *name,
                                const char *value, int lang, int long_style)
{
    int size = 0;
    if (value && value[0]) {
        int64_t pos = avio_tell(pb);
        avio_wb32(pb, 0); /* size */
        ffio_wfourcc(pb, name);
        mov_write_string_data_tag(pb, value, lang, long_style);
        size = update_size(pb, pos);
    }
    return size;
}

/* iTunes meta data list */
static int mov_write_ilst_tag(FIL *pb, MOVMuxContext2 *mov,
                              AVFormatContext2 *s)
{
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */
    ffio_wfourcc(pb, "ilst");

#define LIBAVFORMAT_IDENT       "Lavf"
    mov_write_string_tag(pb, "\251too", LIBAVFORMAT_IDENT, 0, 1);

    return update_size(pb, pos);
}

static int mov_write_itunes_hdlr_tag(FIL *pb, MOVMuxContext2 *mov,
                                     AVFormatContext2 *s)
{
    avio_wb32(pb, 33); /* size */
    ffio_wfourcc(pb, "hdlr");
    avio_wb32(pb, 0);
    avio_wb32(pb, 0);
    ffio_wfourcc(pb, "mdir");
    ffio_wfourcc(pb, "appl");
    avio_wb32(pb, 0);
    avio_wb32(pb, 0);
    avio_w8(pb, 0);
    return 33;
}

/* meta data tags */
static int mov_write_meta_tag(FIL *pb, MOVMuxContext2 *mov,
                              AVFormatContext2 *s)
{
    int size = 0;
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size */

    ffio_wfourcc(pb, "meta");
    avio_wb32(pb, 0);
    mov_write_itunes_hdlr_tag(pb, mov, s);
    mov_write_ilst_tag(pb, mov, s);

    size = update_size(pb, pos);
    return size;
}

static int mov_write_udta_tag(FIL *pb, MOVMuxContext2 *mov,
                              AVFormatContext2 *s)
{
//    int ret;
    int size = 0;
//    uint8_t *buf;

    int64_t pos = avio_tell(pb);
    avio_wb32(pb, size + 8);
    ffio_wfourcc(pb, "udta");

    /* iTunes meta data */
    mov_write_meta_tag(pb, mov, s);

    return update_size(pb, pos);
}

/* one chunk is one or some packets */
static void build_chunks(MOVTrack2 *trk)
{
    int i;
    // first chunk
    MOVIentry2 *chunk = &trk->cluster[0]; /* one temp chunk or one packet */
    // first chunk size
    uint64_t chunkSize = chunk->size; /* last packet size */
    // set start chunk num
    chunk->chunkNum = 1; ///++
    if (trk->chunkCount) /* is 0 and continue */
        return;
    trk->chunkCount = 1;

    /* foreach every packet, entry is increase in mov_packet_write */
    for (i = 1; i<trk->entry; i++){
        /* if one chunk is one packet */
        trk->cluster[i].chunkNum = chunk->chunkNum + 1; ///++
        chunk = &trk->cluster[i];
        chunkSize = chunk->size;
        trk->chunkCount++;
    }
}

static int mov_write_moov_tag(FIL *pb, MOVMuxContext2 *mov,
                                AVFormatContext2 *s)
{
    int i;
    int64_t pos = avio_tell(pb);
    avio_wb32(pb, 0); /* size placeholder*/
    ffio_wfourcc(pb, "moov");

    // track id = stream id + 1

    for (i = 0; i < mov->nb_streams; i++) {
        if (mov->tracks[i].entry <= 0)
            continue;

        mov->tracks[i].time     = mov->time; //real time. 0x7C25B080; // 1970 based -> 1904 based
        /* parse stsc, samples in chunk */
        if (mov->tracks[i].entry) /* entry is increase in mov_write_packet */
            build_chunks(&mov->tracks[i]);
    }

    mov_write_mvhd_tag(pb, mov);

    for (i = 0; i < mov->nb_streams; i++) {
        /* foreach video and audio trak in one mvhd */
        if (mov->tracks[i].entry > 0) {
            int ret = mov_write_trak_tag(s, pb, mov, &(mov->tracks[i])/*, i < s->nb_streams ? s->streams[i] : NULL*/);
            if (ret < 0)
                return ret;
        }
    }

    mov_write_udta_tag(pb, mov, s);
    return update_size(pb, pos);
}

static uint16_t language_code(const char *str)
{
    return (((str[0] - 0x60) & 0x1F) << 10) +
           (((str[1] - 0x60) & 0x1F) <<  5) +
           (( str[2] - 0x60) & 0x1F);
}

////////////////////////////////////////////////////////////////////////////////
static int mov_init(session_unit_t *punit)
{
    int i;

    punit->file = fopen("test.mp4", "wb+");
    if (punit->file == NULL) {
        debug("open output mp4 file error!\n");
        return -1;
    } else {
        debug("open output mp4 file pass\n");
    }

    /**
     * default value
     */
    AVFormatContext2 *s = &punit->format;
    s->priv_data = &punit->mov;
    s->pb = punit->file;
    MOVMuxContext2 *mov = (MOVMuxContext2 *)s->priv_data;
    mov->tracks = (MOVTrack2 *)&punit->track;
    mov->fc = s;
    mov->mode = MODE_MP4;
    mov->nb_streams = 2;

    punit->fmt_buf.buf = g_fmt_buf;
    punit->fmt_buf.curpos = 0;
    punit->fmt_buf.maxlen = sizeof(g_fmt_buf);
    punit->fmt_buf.unit_size = sizeof(MOVIentry2);

    /**
     * for video
     */
    int stream_index = 0;
    mov->tracks[stream_index].par = &punit->par[stream_index];
    mov->tracks[stream_index].par->codec_type = AVMEDIA_TYPE_VIDEO2;
    if (punit->width)
        mov->tracks[stream_index].par->width = punit->width;
    else
        mov->tracks[stream_index].par->width = 1280;
    if (punit->height)
        mov->tracks[stream_index].height = punit->height;
    else
        mov->tracks[stream_index].height = 720;

    mov->tracks[stream_index].cluster = (MOVIentry2 *)punit->fmt_buf.buf;
    mov->tracks[stream_index].max_cluster_num = (FORMAT_BUFFER_LENGTH - CTTS_BUF_SIZE * 2) / punit->fmt_buf.unit_size / 2;
    int curlen = mov->tracks[stream_index].max_cluster_num *  punit->fmt_buf.unit_size;
    punit->fmt_buf.buf += curlen;
    punit->fmt_buf.curpos += curlen;
    punit->cttsbuf = (MOVStts2 *)(punit->fmt_buf.buf); // ctts max num 2048
    punit->fmt_buf.buf += CTTS_BUF_SIZE;
    punit->fmt_buf.curpos += CTTS_BUF_SIZE;

    mov->tracks[stream_index].data_offset = 0;
    //TODO: timescale modify from origin parameter
    mov->tracks[stream_index].timescale = 12800; // not MOV_TIMESCALE;
    mov->tracks[stream_index].track_id = 1; // = stream id + 1
    mov->tracks[stream_index].language = language_code("und");
    mov->tracks[stream_index].priv = punit;
    mov->tracks[stream_index].sample_size = 8 * 1024; // max 64k
    //TODO: set from session_file_t fileinfo
    mov->tracks[stream_index].par->sample_rate = 24000; // 44100;

    /**
     * for audio
     */
    stream_index = 1;
    mov->tracks[stream_index].par = &punit->par[stream_index];
    mov->tracks[stream_index].par->codec_type = AVMEDIA_TYPE_AUDIO2;
    curlen = punit->fmt_buf.maxlen - punit->fmt_buf.curpos - CTTS_BUF_SIZE;
    mov->tracks[stream_index].max_cluster_num = curlen / punit->fmt_buf.unit_size;
    mov->tracks[stream_index].cluster = (MOVIentry2 *)(punit->fmt_buf.buf);

    punit->fmt_buf.buf += curlen;
    punit->fmt_buf.curpos += curlen;
    punit->cttsbuf = (MOVStts2 *)(punit->fmt_buf.buf);
    punit->fmt_buf.buf += CTTS_BUF_SIZE;
    punit->fmt_buf.curpos += CTTS_BUF_SIZE;

    if (punit->fmt_buf.curpos > punit->fmt_buf.maxlen)
        printf("cache buffer overflow!\n");

    mov->tracks[stream_index].data_offset = 0;
    //TODO: timescale modify from origin parameter
    mov->tracks[stream_index].timescale = 44100; // is audio sample rate
    mov->tracks[stream_index].track_id = 2;
    mov->tracks[stream_index].language = language_code("und");
    mov->tracks[stream_index].priv = (void *)punit;

    for (i = 0; i < mov->nb_streams; i++) {
        mov->tracks[i].start_dts  = AV_NOPTS_VALUE;
        mov->tracks[i].start_cts  = AV_NOPTS_VALUE;
        mov->tracks[i].end_pts    = AV_NOPTS_VALUE;
        mov->tracks[i].dts_shift  = AV_NOPTS_VALUE;
    }
    return 0;
}

static int mov_write_header(session_unit_t *punit)
{
    AVFormatContext2 *s = &punit->format;
    FIL *pb = s->pb;
    MOVMuxContext2 *mov = s->priv_data;

    mov_write_ftyp_tag(pb, &punit->format);
    mov_write_mdat_tag(pb, &punit->format);
    //TODO: get current real time
    mov->time += 0x7C25B080; // 1970 based -> 1904 based
    return 0;
}

static int module_stop(void);
static int mov_write_packet(FIL *pb, AVFormatContext2 *s, AVPacket2 *pkt)
{
    MOVMuxContext2 *mov = s->priv_data;
    MOVTrack2 *trk = &mov->tracks[pkt->stream_index]; // stream_index and pkt set in SessionRun()
    session_unit_t *punit = trk->priv;
//    AVCodecParameters2 *par = trk->par;
    int size = pkt->size;
//     AVProducerReferenceTime *prft; // used for wallclocks
    unsigned int samples_in_chunk = 0; // if have chunk
    int ret = 0;
//    int offset = 0;
//    int prft_size;
//    uint8_t *reformatted_data = NULL;

    // modified from mov_write_single_packet(), ff_mov_write_packet() of ffmpeg

    if (trk->entry + 1 >= trk->max_cluster_num) {
        /* if the mp4enc cache buffer full */
        module_stop();
        return 0;
    }

    /* check the pts and duration */
    ret = check_pkt(s, pkt);
    if (ret < 0)
        return ret;

#if 0 // for stsc, several packet(samples) in chunk
    //TODO: add this
    // if (trk->sample_size)
    //     samples_in_chunk = size / trk->sample_size;
    // else
#endif
        samples_in_chunk = 1;

    avio_write(pb, pkt->data, size);

    /* set per packet */
    trk->cluster[trk->entry].pos              = avio_tell(pb) - size; /*packet position in mp4 file */
    trk->cluster[trk->entry].samples_in_chunk = samples_in_chunk;
    trk->cluster[trk->entry].chunkNum         = 0;      /* chunkNum set in build_chunks() */
    trk->cluster[trk->entry].size             = size;   /* packet size */
    trk->cluster[trk->entry].entries          = samples_in_chunk;
    trk->cluster[trk->entry].dts              = pkt->dts;
    trk->cluster[trk->entry].pts              = pkt->pts;

    trk->start_dts = 0;
    /* if first enter, set the first dts one time */
    if (trk->start_dts == AV_NOPTS_VALUE) {
        trk->start_dts = pkt->dts;
    }

    if (pkt->pts == AV_NOPTS_VALUE) {
        pkt->pts = pkt->dts;
    }

    if (trk->start_dts == AV_NOPTS_VALUE) {
        trk->start_dts = pkt->dts;
    }

    trk->track_duration = pkt->dts - trk->start_dts + pkt->duration;

    if (pkt->dts != pkt->pts)
        trk->flags |= MOV_TRACK_CTTS;

    /* add cts */
   trk->cluster[trk->entry].cts   = pkt->pts - pkt->dts;
   trk->cluster[trk->entry].flags = 0;
   if (trk->start_cts == AV_NOPTS_VALUE)
       trk->start_cts = pkt->pts - pkt->dts;

   trk->end_pts = trk->cluster[trk->entry].dts +
                   trk->cluster[trk->entry].cts + pkt->duration;

#if 1
    /* set default key frame 1 */
    //TODO: set key frame
    static int zjpacketflag = 0;
    if (zjpacketflag == 0) {
        trk->cluster[trk->entry].flags = MOV_SYNC_SAMPLE;
        zjpacketflag = 1;
    }
#endif

    /* key frame */
    if (pkt->flags & AV_PKT_FLAG_KEY) {
        trk->cluster[trk->entry].flags = MOV_SYNC_SAMPLE;
        if (trk->cluster[trk->entry].flags & MOV_SYNC_SAMPLE)
            trk->has_keyframes++;
    }

    /* set real time */
//     memcpy(&trk->cluster[trk->entry].prft, prft, prft_size);

    trk->entry++; /* the packet total count */
    trk->sample_count += samples_in_chunk; // samples_in_chunk is always 1
    mov->mdat_size    += size;

    return size;
}

static int mov_write_trailer(session_unit_t *punit)
{
    FILE *pb = punit->file;
    AVFormatContext2 *s = &punit->format;
    MOVMuxContext2 *mov = (MOVMuxContext2 *)s->priv_data;

    int64_t moov_pos = avio_tell(pb);

    avio_seek(pb, mov->mdat_pos, SEEK_SET);
    avio_wb32(pb, mov->mdat_size + 8);

    avio_seek(pb, moov_pos, SEEK_SET);

    mov_write_moov_tag(pb, mov, s);

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
static uint32_t alastLowPart; // for duration calc
static uint32_t vlastLowPart; // for duration calc

static int module_init(void)
{
    punit->start_flag = 0;

    if (target)
        target->init();

    memset(punit, 0, sizeof(session_unit_t));

    return 0;
}

static int module_start(void *param)
{
    mp4vmdatalen = 0;
    mp4amdatalen = 0;

    if (target)
        target->start(NULL);

    if (1 == punit->start_flag) // only start once for both video and audio request
        return 0;

    memset(punit, 0, sizeof(session_unit_t));
    punit->start_flag = 1;

    punit->width = 1280;
    punit->height = 720;
    mov_init(punit);
    mov_write_header(punit);

    return 0;
}

static int apts = 0;
static int vpts = 0;
static int module_run(void)
{
    if (target)
        target->run();

    if (!mp4amdatalen && !mp4vmdatalen) // 如果没有数据，未获取到锁
        return 0;

    long res = 0;

    if (mp4amdatalen || mp4vmdatalen) {
        if (mp4amdatalen) {
           // if audio
           AVPacket2 packet = {
               .data = amdata,
               .size = mp4amdatalen,
               .pts = apts,
               .dts = apts,
           };
           packet.stream_index = 1;
           // ffmpeg 音频使用1s对等的时间基默认是采样率，如44100
           // 1s    1000ms Nms
           // 44100 44100  (N * 44100) / 1000
           // 1000/12800 = N / x, x = N / (1000 / 44100) = (N * 44100) / 1000
           apts += (40 * 44100) / 1000;
           packet.duration = (apts - alastLowPart);
           alastLowPart = apts;
           if (punit->movstate != MOV_STATE_HEADER) {
               AVFormatContext2 *s = &punit->format;
               //TODO: add packet.duration
               mov_write_packet(punit->file, s, &packet);
           }
        } else { // mp4vmdatalen
           // if video
           AVPacket2 packet = {
               .data = vmdata,
               .size = mp4vmdatalen,
               .pts = vpts,
               .dts = vpts,
           };
           if (punit->movstate == MOV_STATE_HEADER) {
               /* parse pps sps vps of h264 or hevc in the first packet */
               /* only parse one first packet */
               int ret = parse_sps_pps_vps(vmdata, mp4vmdatalen,
                                           punit->metadata, &punit->metalen);
               if (ret == 0)
                   punit->movstate = MOV_STATE_PACKET;
           }

           packet.stream_index = 0;
           // ffmpeg 视频使用1s对等的默认时间基是12800
           // 1s    1000ms Nms
           // 12800 12800  (N * 12800) / 1000
           // 1000/12800 = N / x, x = N / (1000 / 12800) = (N * 12800) / 1000
           vpts += ((40 * 12800) / 1000);
           packet.duration = (vpts - vlastLowPart);
           vlastLowPart = vpts;
           if (punit->movstate != MOV_STATE_HEADER) {
               AVFormatContext2 *s = &punit->format;
               //TODO: add packet.duration
               mov_write_packet(punit->file, s, &packet);
           }
        };

        if (mp4amdatalen)
            mp4amdatalen = 0; // 释放锁
        else if (mp4vmdatalen)
            mp4vmdatalen = 0;
    }

    return 0;
}

static int module_stop(void)
{
    if (target)
        target->stop();
    if (0 == punit->start_flag) // only stop once for both video and audio request
        return 0;
    punit->start_flag = 0;

    mov_write_trailer(punit);

    fclose(punit->file);

    memset(punit, 0, sizeof(session_unit_t));

    return 0;
}

static int module_exit(void)
{
    if (target)
        target->exit();

    return 0;
}

static int module_settarget(void *mtarget)
{
    target = (struct module *)mtarget;

    return 0;
}

// flag: 1 is video, 2 is audio
static int module_putdata(void *data, int len, int flag)
{
    if (!data || len > MDATA_LEN)
        return 0;

    if (flag == 1 && !mp4vmdatalen) { // 如果有数据，未获取到锁
        memcpy(vmdata, data, len);
        mp4vmdatalen = len;
    }
    if (flag == 2 && !mp4amdatalen) { // 如果有数据，未获取到锁
        memcpy(amdata, data, len);
        mp4amdatalen = len;
    }

    return 0;
}

struct module g_module_mp4mux = {
    "mp4mux", module_init,
    module_start, module_run,
    module_stop, module_exit,
    module_settarget, module_putdata
};
